home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / chrome / calendar.jar / content / calendar / calendar-unifinder.js < prev    next >
Text File  |  2008-02-19  |  32KB  |  950 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is OEone Calendar Code, released October 31st, 2001.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * OEone Corporation.
  18.  * Portions created by the Initial Developer are Copyright (C) 2001
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Garth Smedley <garths@oeone.com>
  23.  *   Mike Potter <mikep@oeone.com>
  24.  *   Chris Charabaruk <coldacid@meldstar.com>
  25.  *   Colin Phillips <colinp@oeone.com>
  26.  *   ArentJan Banck <ajbanck@planet.nl>
  27.  *   Eric Belhaire <eric.belhaire@ief.u-psud.fr>
  28.  *   Matthew Willis <mattwillis@gmail.com>
  29.  *   Michiel van Leeuwen <mvl@exedo.nl>
  30.  *   Joey Minta <jminta@gmail.com>
  31.  *   Dan Mosedale <dan.mosedale@oracle.com>
  32.  *   Michael Buettner <michael.buettner@sun.com>
  33.  *   Philipp Kewisch <mozilla@kewis.ch>
  34.  *
  35.  * Alternatively, the contents of this file may be used under the terms of
  36.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  37.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  38.  * in which case the provisions of the GPL or the LGPL are applicable instead
  39.  * of those above. If you wish to allow use of your version of this file only
  40.  * under the terms of either the GPL or the LGPL, and not to allow others to
  41.  * use your version of this file under the terms of the MPL, indicate your
  42.  * decision by deleting the provisions above and replace them with the notice
  43.  * and other provisions required by the GPL or the LGPL. If you do not delete
  44.  * the provisions above, a recipient may use your version of this file under
  45.  * the terms of any one of the MPL, the GPL or the LGPL.
  46.  *
  47.  * ***** END LICENSE BLOCK ***** */
  48.  
  49. /**
  50.  * U N I F I N D E R
  51.  *
  52.  * This is a hacked in interface to the unifinder. We will need to
  53.  * improve this to make it usable in general.
  54.  */
  55. var kEventStatusOrder = ["TENTATIVE", "CONFIRMED", "CANCELLED"];
  56.  
  57. // Set this to true when the calendar event tree is clicked to allow for
  58. // multiple selection
  59. var gCalendarEventTreeClicked = false;
  60.  
  61. // Store the start and enddate, because the providers can't be trusted when
  62. // dealing with all-day events. So we need to filter later. See bug 306157
  63. var gStartDate;
  64. var gEndDate;
  65.  
  66. var kDefaultTimezone;
  67. var gUnifinderNeedsRefresh = true;
  68.  
  69. function isUnifinderHidden() {
  70.     return document.getElementById("bottom-events-box").hidden;
  71. }
  72.  
  73. // Extra check to see if the events are in the daterange. Some providers
  74. // are broken when looking at all-day events.
  75. function fixAlldayDates(aItem) {
  76.     // Using .compare on the views start and end, not on the events dates,
  77.     // because .compare uses the timezone of the datetime it is called on.
  78.     // The view's timezone is what is important here.
  79.     return ((!gEndDate || gEndDate.compare(aItem.startDate) >= 0) &&
  80.             (!gStartDate || gStartDate.compare(aItem.endDate) < 0));
  81. }
  82.  
  83. /**
  84.  * Observer for the calendar event data source. This keeps the unifinder
  85.  * display up to date when the calendar event data is changed
  86.  */
  87.  
  88. var unifinderObserver = {
  89.     mInBatch: false,
  90.  
  91.     QueryInterface: function uO_QueryInterface (aIID) {
  92.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  93.             !aIID.equals(Components.interfaces.calICompositeObserver) &&
  94.             !aIID.equals(Components.interfaces.calIObserver)) {
  95.             throw Components.results.NS_ERROR_NO_INTERFACE;
  96.         }
  97.  
  98.         return this;
  99.     },
  100.  
  101.     // calIObserver:
  102.     onStartBatch: function uO_onStartBatch() {
  103.         this.mInBatch = true;
  104.     },
  105.  
  106.     onEndBatch: function uO_onEndBatch() {
  107.         this.mInBatch = false;
  108.         refreshEventTree();
  109.     },
  110.  
  111.     onLoad: function uO_onLoad() {
  112.         if (isUnifinderHidden() && !gUnifinderNeedsRefresh) {
  113.             // If the unifinder is hidden, all further item operations might
  114.             // produce invalid entries in the unifinder. From now on, ignore
  115.             // those operations and refresh as soon as the unifinder is shown
  116.             // again.
  117.             gUnifinderNeedsRefresh = true;
  118.             unifinderTreeView.clearItems();
  119.         }
  120.         if (!this.mInBatch) {
  121.             refreshEventTree();
  122.         }
  123.     },
  124.  
  125.     onAddItem: function uO_onAddItem(aItem) {
  126.         if (isEvent(aItem) &&
  127.             !this.mInBatch &&
  128.             !gUnifinderNeedsRefresh &&
  129.             isItemInFilter(aItem)) {
  130.             this.addItemToTree(aItem);
  131.         }
  132.     },
  133.  
  134.     onModifyItem: function uO_onModifyItem(aNewItem, aOldItem) {
  135.         this.onDeleteItem(aOldItem);
  136.         this.onAddItem(aNewItem);
  137.     },
  138.  
  139.     onDeleteItem: function uO_onDeleteItem(aDeletedItem) {
  140.         if (isEvent(aDeletedItem) && !this.mInBatch && !gUnifinderNeedsRefresh) {
  141.             this.removeItemFromTree(aDeletedItem);
  142.         }
  143.     },
  144.  
  145.     // It is safe to call these for any event.  The functions will determine
  146.     // whether or not anything actually needs to be done to the tree
  147.     addItemToTree: function uO_addItemToTree(aItem) {
  148.         var items;
  149.         if (gStartDate && gEndDate) {
  150.             items = aItem.getOccurrencesBetween(gStartDate, gEndDate, {});
  151.         } else {
  152.             items = [aItem];
  153.         }
  154.         unifinderTreeView.addItems(items.filter(fixAlldayDates));
  155.     },
  156.     removeItemFromTree: function uO_removeItemFromTree(aItem) {
  157.         var items;
  158.         if (gStartDate && gEndDate && (aItem.parentItem == aItem)) {
  159.             items = aItem.getOccurrencesBetween(gStartDate, gEndDate, {});
  160.         } else {
  161.             items = [aItem];
  162.         }
  163.         unifinderTreeView.removeItems(items.filter(fixAlldayDates));
  164.     },
  165.  
  166.     onError: function uO_onError(aErrNo, aMessage) {},
  167.  
  168.     onPropertyChanged: function uO_onPropertyChanged(aCalendar, aName, aValue, aOldValue) {},
  169.     onPropertyDeleting: function uO_onPropertyDeleting(aCalendar, aName) {},
  170.  
  171.     // calICompositeObserver:
  172.     onCalendarAdded: function uO_onCalendarAdded(aDeletedItem) {
  173.         if (!this.mInBatch) {
  174.             refreshEventTree();
  175.         }
  176.     },
  177.  
  178.     onCalendarRemoved: function uO_onCalendarRemoved(aDeletedItem) {
  179.         // TODO only remove such items that belong to the calendar
  180.         if (!this.mInBatch) {
  181.             refreshEventTree();
  182.         }
  183.     },
  184.  
  185.     onDefaultCalendarChanged: function uO_onDefaultCalendarChanged(aNewDefaultCalendar) {}
  186. };
  187.  
  188. /**
  189.  * Called when the calendar is loaded
  190.  */
  191. function prepareCalendarUnifinder() {
  192.     // Only load once
  193.     window.removeEventListener("load", prepareCalendarUnifinder, false);
  194.     var unifinderTree = document.getElementById("unifinder-search-results-tree");
  195.  
  196.     // Check if this is not the hidden window, which has no UI elements
  197.     if (unifinderTree) {
  198.         // set up our calendar event observer
  199.         var ccalendar = getCompositeCalendar();
  200.         ccalendar.addObserver(unifinderObserver);
  201.  
  202.         kDefaultTimezone = calendarDefaultTimezone();
  203.  
  204.         // Set up the unifinder views.
  205.         unifinderTreeView.treeElement = unifinderTree;
  206.         unifinderTree.view = unifinderTreeView;
  207.  
  208.         // Listen for changes in the selected day, so we can update if need be
  209.         var viewDeck = getViewDeck();
  210.         viewDeck.addEventListener("dayselect", unifinderDaySelect, false);
  211.         viewDeck.addEventListener("itemselect", unifinderItemSelect, true);
  212.  
  213.         // Set up sortDirection and sortActive, in case it persisted
  214.         var active = document.getElementById("unifinder-search-results-tree-cols")
  215.                              .getElementsByAttribute("sortActive", "true");
  216.         if (active.length > 0) {
  217.             unifinderTreeView.selectedColumn = active[0].id;
  218.             unifinderTreeView.sortDirection = active[0].getAttribute("sortDirection");
  219.         }
  220.  
  221.         // Display something upon first load. onLoad doesn't work properly for
  222.         // observers
  223.         if (!isUnifinderHidden()) {
  224.             gUnifinderNeedsRefresh = false;
  225.             refreshEventTree();
  226.         }
  227.     }
  228. }
  229.  
  230. /**
  231.  * Called when the calendar is unloaded
  232.  */
  233. function finishCalendarUnifinder() {
  234.     var ccalendar = getCompositeCalendar();
  235.     ccalendar.removeObserver(unifinderObserver);
  236.  
  237.     var viewDeck = getViewDeck();
  238.     if (viewDeck) {
  239.         viewDeck.removeEventListener("dayselect", unifinderDaySelect, false);
  240.         viewDeck.removeEventListener("itemselect", unifinderItemSelect, true);
  241.     }
  242. }
  243.  
  244. /**
  245.  * Event listeners for dayselect and itemselect events
  246.  */
  247. function unifinderDaySelect() {
  248.     var filterList = document.getElementById("event-filter-menulist");
  249.     if (filterList.selectedItem.value == "current") {
  250.         refreshEventTree();
  251.     }
  252. }
  253.  
  254. function unifinderItemSelect(aEvent) {
  255.     unifinderTreeView.setSelectedItems(aEvent.detail);
  256. }
  257.  
  258. /**
  259.  * Helper function to display event datetimes in the unifinder
  260.  */
  261. function formatUnifinderEventDateTime(aDatetime) {
  262.     var dateFormatter = Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
  263.                                   .getService(Components.interfaces.calIDateTimeFormatter);
  264.     return dateFormatter.formatDateTime(aDatetime.getInTimezone(kDefaultTimezone));
  265. }
  266.  
  267. /**
  268.  *  This is called from the unifinder when a key is pressed in the search field
  269.  */
  270. var gSearchTimeout = null;
  271.  
  272. function searchKeyPress(searchTextItem, event) {
  273.     // 13 == return
  274.     if (event && event.keyCode == 13) {
  275.         clearSearchTimer();
  276.         refreshEventTree();
  277.         return;
  278.     }
  279.  
  280.     // Always clear the old one first
  281.     clearSearchTimer();
  282.  
  283.     // Make a new timer
  284.     gSearchTimeout = setTimeout("refreshEventTree()", 400);
  285. }
  286.  
  287. function clearSearchTimer() {
  288.    if (gSearchTimeout) {
  289.       clearTimeout(gSearchTimeout);
  290.       gSearchTimeout = null;
  291.    }
  292. }
  293.  
  294. /**
  295.  * Unifinder event handlers (click,select,etc)
  296.  */
  297. function unifinderDoubleClick(event) {
  298.     // We only care about button 0 (left click) events
  299.     if (event.button != 0) {
  300.         return;
  301.     }
  302.  
  303.     // find event by id
  304.     var calendarEvent = unifinderTreeView.getItemFromEvent(event);
  305.  
  306.     if (calendarEvent != null) {
  307.         modifyEventWithDialog(getOccurrenceOrParent(calendarEvent));
  308.     } else {
  309.         createEventWithDialog();
  310.     }
  311. }
  312.  
  313. function unifinderSelect(event) {
  314.     var tree = unifinderTreeView.treeElement;
  315.     if (!tree.view.selection || tree.view.selection.getRangeCount() == 0) {
  316.         return;
  317.     }
  318.  
  319.     var selectedItems = [];
  320.     gCalendarEventTreeClicked = true;
  321.  
  322.     // Get the selected events from the tree
  323.     var start = {};
  324.     var end = {};
  325.     var numRanges = tree.view.selection.getRangeCount();
  326.  
  327.     for (var t = 0; t < numRanges; t++) {
  328.         tree.view.selection.getRangeAt(t, start, end);
  329.  
  330.         for (var v = start.value; v <= end.value; v++) {
  331.             try {
  332.                 selectedItems.push(unifinderTreeView.getItemAt(v));
  333.             } catch (e) {
  334.                WARN("Error getting Event from row: " + e + "\n");
  335.             }
  336.         }
  337.     }
  338.  
  339.     if (selectedItems.length == 1) {
  340.         // Go to the day of the selected item in the current view.
  341.         currentView().goToDay(selectedItems[0].startDate);
  342.     }
  343.  
  344.     // Set up the selected items in the view. Pass in true, so we don't end
  345.     // up in a circular loop
  346.     currentView().setSelectedItems(selectedItems.length, selectedItems, true);
  347.     onSelectionChanged({detail: selectedItems});
  348. }
  349.  
  350. function unifinderKeyPress(aEvent) {
  351.     const kKE = Components.interfaces.nsIDOMKeyEvent;
  352.     switch (aEvent.keyCode) {
  353.         case 13:
  354.             // Enter, edit the event
  355.             editSelectedEvents();
  356.             break;
  357.         case kKE.DOM_VK_BACK_SPACE:
  358.         case kKE.DOM_VK_DELETE:
  359.             deleteSelectedEvents();
  360.             break;
  361.     }
  362. }
  363.  
  364. /**
  365.  * Tree controller for unifinder search results
  366.  */
  367. var unifinderTreeView = {
  368.  
  369.     tree: null,
  370.     treeElement: null,
  371.     doingSelection: false,
  372.  
  373.     /**
  374.      * Event functions
  375.      */
  376.  
  377.     eventArray: [],
  378.     eventIndexMap: {},
  379.  
  380.     addItems: function uTV_addItems(aItemArray, aDontSort) {
  381.         this.eventArray = this.eventArray.concat(aItemArray);
  382.         if (this.tree) {
  383.             var newCount = this.eventArray.length - aItemArray.length - 1;
  384.             this.tree.rowCountChanged(newCount, aItemArray.length);
  385.         }
  386.  
  387.         if (aDontSort) {
  388.             this.calculateIndexMap();
  389.         } else {
  390.             this.sortItems();
  391.         }
  392.     },
  393.  
  394.     removeItems: function uTV_removeItems(aItemArray) {
  395.         for each (var item in aItemArray) {
  396.             var row = this.getItemRow(item);
  397.             if (row > -1) {
  398.                 this.eventArray.splice(row, 1);
  399.                 if (this.tree) {
  400.                     this.tree.rowCountChanged(row, -1);
  401.                 }
  402.             }
  403.         }
  404.         this.calculateIndexMap();
  405.     },
  406.  
  407.     clearItems: function uTV_clearItems() {
  408.         var oldCount = this.eventArray.length;
  409.         this.eventArray = [];
  410.         if (this.tree) {
  411.             this.tree.rowCountChanged(0, -oldCount);
  412.         }
  413.         this.calculateIndexMap();
  414.     },
  415.  
  416.     setItems: function uTV_setItems(aItemArray, aDontSort) {
  417.         var oldCount = this.eventArray.length;
  418.         this.eventArray = aItemArray.slice(0);
  419.         if (this.tree) {
  420.             this.tree.rowCountChanged(0, (this.eventArray.length - oldCount));
  421.         }
  422.        
  423.         if (aDontSort) {
  424.             //this.calculateIndexMap();
  425.         } else {
  426.             this.sortItems();
  427.         }
  428.     },
  429.  
  430.     calculateIndexMap: function uTV_calculateIndexMap() {
  431.         this.eventIndexMap = {};
  432.         for (var i = 0 ; i < this.eventArray.length; i++) {
  433.             this.eventIndexMap[this.eventArray[i].hashId] = i;
  434.         }
  435.  
  436.         if (this.tree) {
  437.             this.tree.invalidate();
  438.         }
  439.     },
  440.  
  441.     sortItems: function uTV_sortItems() {
  442.         // Get a current locale string collator for compareEvents
  443.         if (!this.localeCollator) {
  444.             var localeService =
  445.                 Components
  446.                 .classes["@mozilla.org/intl/nslocaleservice;1"]
  447.                 .getService(Components.interfaces.nsILocaleService);
  448.             this.localeCollator =
  449.                 Components
  450.                 .classes["@mozilla.org/intl/collation-factory;1"]
  451.                 .getService(Components.interfaces.nsICollationFactory)
  452.                 .CreateCollation(localeService.getApplicationLocale());
  453.         }
  454.  
  455.         this.sortStartedTime = new Date().getTime(); // for null/0 dates in sort
  456.  
  457.         // sort (key,item) entries
  458.         var entries = this.eventArray.map(sortEntry);
  459.         entries.sort(sortEntryComparer(this));
  460.         this.eventArray = entries.map(sortEntryItem);
  461.  
  462.         this.calculateIndexMap();
  463.     },
  464.  
  465.     getItemRow: function uTV_getItemRow(item) {
  466.         if (this.eventIndexMap[item.hashId] === undefined) {
  467.             return -1;
  468.         }
  469.         return this.eventIndexMap[item.hashId];
  470.     },
  471.  
  472.     getItemAt: function uTV_getItemAt(aRow) {
  473.         return this.eventArray[aRow];
  474.     },
  475.  
  476.     /**
  477.      * Get the calendar item from the given event
  478.      */
  479.     getItemFromEvent: function uTV_getItemFromEvent(event) {
  480.         var row = this.tree.getRowAt(event.clientX, event.clientY);
  481.  
  482.         if (row > -1) {
  483.             return this.getItemAt(row);
  484.         }
  485.         return null;
  486.     },
  487.  
  488.     setSelectedItems: function uTV_setSelectedItems(aItemArray) {
  489.         if (this.doingSelection || !this.tree) {
  490.             return;
  491.         }
  492.  
  493.         this.doingSelection = true;
  494.  
  495.         // If no items were passed, get the selected items from the view.
  496.         aItemArray = aItemArray || currentView().getSelectedItems({});
  497.  
  498.         /**
  499.          * The following is a brutal hack, caused by
  500.          * http://lxr.mozilla.org/mozilla1.0/source/layout/xul/base/src/tree/src/nsTreeSelection.cpp#555
  501.          * and described in bug 168211
  502.          * http://bugzilla.mozilla.org/show_bug.cgi?id=168211
  503.          * Do NOT remove anything in the next 3 lines, or the selection in the tree will not work.
  504.          */
  505.         this.treeElement.onselect = null;
  506.         this.treeElement.removeEventListener("select", unifinderSelect, true);
  507.         this.tree.view.selection.selectEventsSuppressed = true;
  508.         this.tree.view.selection.clearSelection();
  509.  
  510.         if (aItemArray && aItemArray.length == 1) {
  511.             // If only one item is selected, scroll to it
  512.             var rowToScrollTo = this.getItemRow(aItemArray[0]);
  513.             if (rowToScrollTo > -1) {
  514.                this.tree.ensureRowIsVisible(rowToScrollTo);
  515.                this.tree.view.selection.select(rowToScrollTo);
  516.             }
  517.         } else if (aItemArray && aItemArray.length > 1) {
  518.             // If there is more than one item, just select them all.
  519.             for (var i in aItemArray) {
  520.                 var row = this.getItemRow(aItemArray[i]);
  521.                 this.tree.view.selection.rangedSelect(row, row, true);
  522.             }
  523.         }
  524.  
  525.         // This needs to be in a setTimeout
  526.         setTimeout("unifinderTreeView.resetAllowSelection()", 1);
  527.     },
  528.  
  529.     resetAllowSelection: function uTV_resetAllowSelection() {
  530.         if (!this.tree) {
  531.             return;
  532.         }
  533.         /**
  534.          * Do not change anything in the following lines, they are needed as
  535.          * described in the selection observer above
  536.          */
  537.         this.doingSelection = false;
  538.  
  539.         this.tree.view.selection.selectEventsSuppressed = false;
  540.         this.treeElement.addEventListener("select", unifinderSelect, true);
  541.     },
  542.  
  543.     /**
  544.      * Tree View Implementation
  545.      */
  546.     get rowCount uTV_getRowCount() {
  547.         return this.eventArray.length;
  548.     },
  549.  
  550.     getRowProperties: function uTV_getRowProperties() {},
  551.     getCellProperties: function uTV_getCellProperties() {},
  552.     getColumnProperties: function uTV_getColumnProperties() {},
  553.  
  554.     isContainer: function uTV_isContainer() {
  555.         return false;
  556.     },
  557.  
  558.     isContainerOpen: function uTV_isContainerOpen(aRow) {
  559.         return false;
  560.     },
  561.  
  562.     isContainerEmpty: function uTV_isContainerEmpty(aRow) {
  563.         return false;
  564.     },
  565.  
  566.     isSeparator: function uTV_isSeparator(aRow) {
  567.         return false;
  568.     },
  569.  
  570.     isSorted: function uTV_isSorted(aRow) {
  571.         return false;
  572.     },
  573.  
  574.     canDrop: function uTV_canDrop(aRow, aOrientation) {
  575.         return false;
  576.     },
  577.  
  578.     drop: function uTV_drop(aRow, aOrientation) {},
  579.  
  580.     getParentIndex: function uTV_getParentIndex(aRow) {
  581.         return -1;
  582.     },
  583.  
  584.     hasNextSibling: function uTV_hasNextSibling(aRow, aAfterIndex) {},
  585.  
  586.     getLevel: function uTV_getLevel(aRow) {
  587.         return 0;
  588.     },
  589.  
  590.     getImageSrc: function uTV_getImageSrc(aRow, aOrientation) {},
  591.  
  592.     getProgressMode: function uTV_getProgressMode(aRow, aCol) {},
  593.  
  594.     getCellValue: function uTV_getCellValue(aRow, aCol) {
  595.         return null;
  596.     },
  597.  
  598.     getCellText: function uTV_getCellText(row, column) {
  599.         calendarEvent = this.eventArray[row];
  600.  
  601.         switch (column.id) {
  602.             case "unifinder-search-results-tree-col-title":
  603.                 return calendarEvent.title;
  604.  
  605.             case "unifinder-search-results-tree-col-startdate":
  606.                 return formatUnifinderEventDateTime(calendarEvent.startDate);
  607.  
  608.             case "unifinder-search-results-tree-col-enddate":
  609.                 var eventEndDate = calendarEvent.endDate.clone();
  610.                 // XXX reimplement
  611.                 //var eventEndDate = getCurrentNextOrPreviousRecurrence(calendarEvent);
  612.                 if (calendarEvent.startDate.isDate) {
  613.                     // display enddate is ical enddate - 1
  614.                     eventEndDate.day = eventEndDate.day - 1;
  615.                 }
  616.                 return formatUnifinderEventDateTime(eventEndDate);
  617.  
  618.             case "unifinder-search-results-tree-col-categories":
  619.                 return calendarEvent.getProperty("CATEGORIES");
  620.  
  621.             case "unifinder-search-results-tree-col-location":
  622.                 return calendarEvent.getProperty("LOCATION");
  623.  
  624.             case "unifinder-search-results-tree-col-status":
  625.                 return getEventStatusString(calendarEvent);
  626.  
  627.             case "unifinder-search-results-tree-col-calendarname":
  628.                 return calendarEvent.calendar.name;
  629.  
  630.             default:
  631.                 return false;
  632.         }
  633.     },
  634.  
  635.     setTree: function uTV_setTree(tree) {
  636.         this.tree = tree;
  637.     },
  638.  
  639.     toggleOpenState: function uTV_toggleOpenState(aRow) {},
  640.  
  641.     cycleHeader: function uTV_cycleHeader(col) {
  642.  
  643.         var sortActive = col.element.getAttribute("sortActive");
  644.         this.selectedColumn = col.id;
  645.         this.sortDirection = col.element.getAttribute("sortDirection");
  646.  
  647.         if (sortActive != "true") {
  648.             var unifinder = document.getElementById("unifinder-search-results-tree");
  649.             var treeCols = unifinder.getElementsByTagName("treecol");
  650.             for (var i = 0; i < treeCols.length; i++) {
  651.                 treeCols[i].removeAttribute("sortActive");
  652.                 treeCols[i].removeAttribute("sortDirection");
  653.             }
  654.             this.sortDirection = "ascending";
  655.         } else {
  656.             if (!this.sortDirection || this.sortDirection == "descending") {
  657.                 this.sortDirection = "ascending";
  658.             } else {
  659.                 this.sortDirection = "descending";
  660.             }
  661.         }
  662.         col.element.setAttribute("sortActive", "true");
  663.         col.element.setAttribute("sortDirection", this.sortDirection);
  664.  
  665.         this.sortItems();
  666.     },
  667.  
  668.     isEditable: function uTV_isEditable(aRow, aCol) {
  669.         return false;
  670.     },
  671.  
  672.     setCellValue: function uTV_setCellValue(aRow, aCol, aValue) {},
  673.     setCellText: function uTV_setCellText(aRow, aCol, aValue) {},
  674.  
  675.     performAction: function uTV_performAction(aAction) {},
  676.  
  677.     performActionOnRow: function uTV_performActionOnRow(aAction, aRow) {},
  678.  
  679.     performActionOnCell: function uTV_performActionOnCell(aAction, aRow, aCol) {},
  680.  
  681.     selectedColumn: null,
  682.     sortDirection: null,
  683.     sortStartedTime: new Date().getTime(), // updated just before sort
  684.     outParameter: new Object() // used to obtain dates during sort
  685. };
  686.  
  687. function getEventSortKey(calEvent) {
  688.     switch(unifinderTreeView.selectedColumn) {
  689.         case "unifinder-search-results-tree-col-title":
  690.             return calEvent.title || "";
  691.  
  692.         case "unifinder-search-results-tree-col-startdate":
  693.             return nativeTimeOrNow(calEvent.startDate);
  694.  
  695.         case "unifinder-search-results-tree-col-enddate":
  696.             return nativeTimeOrNow(calEvent.endDate);
  697.  
  698.         case "unifinder-search-results-tree-col-categories":
  699.             return calEvent.getProperty("CATEGORIES") || "";
  700.  
  701.         case "unifinder-search-results-tree-col-location":
  702.             return calEvent.getProperty("LOCATION") || "";
  703.  
  704.         case "unifinder-search-results-tree-col-status":
  705.             return calEvent.status || "";
  706.  
  707.         case "unifinder-search-results-tree-col-calendarname":
  708.             return calEvent.calendar.name || "";
  709.  
  710.         default:
  711.             return null;
  712.     }
  713. }
  714.  
  715. function sortEntry (aItem) {
  716.     return {mSortKey : getEventSortKey(aItem), mItem: aItem};
  717. }
  718. function sortEntryItem(sortEntry) { 
  719.     return sortEntry.mItem;
  720. }
  721. function sortEntryKey(sortEntry) {
  722.     return sortEntry.mSortKey;
  723. }
  724.  
  725.  
  726. function sortEntryComparer(unifinderTreeView) {
  727.     var modifier = (unifinderTreeView.sortDirection == "descending" ? -1 : 1);
  728.     var collator = unifinderTreeView.localeCollator;
  729.     switch (unifinderTreeView.selectedColumn) {
  730.         case "unifinder-search-results-tree-col-startdate":
  731.         case "unifinder-search-results-tree-col-enddate":
  732.             function compareTimes(sortEntryA, sortEntryB) { 
  733.                 var nsA = sortEntryKey(sortEntryA);
  734.                 var nsB = sortEntryKey(sortEntryB);
  735.                 return compareNativeTime(nsA, nsB) * modifier;
  736.             }
  737.             return compareTimes;
  738.  
  739.         case "unifinder-search-results-tree-col-title":
  740.         case "unifinder-search-results-tree-col-categories":
  741.         case "unifinder-search-results-tree-col-location":
  742.         case "unifinder-search-results-tree-col-status":
  743.         case "unifinder-search-results-tree-col-calendarname":
  744.             function compareStrings(sortEntryA, sortEntryB) { 
  745.                 var sA = sortEntryKey(sortEntryA);
  746.                 var sB = sortEntryKey(sortEntryB);
  747.                 if (sA.length == 0 || sB.length == 0) {
  748.                     // sort empty values to end (so when users first sort by a
  749.                     // column, they can see and find the desired values in that
  750.                     // column without scrolling past all the empty values).
  751.                     return -(sA.length - sB.length) * modifier;
  752.                 }
  753.                 var comparison = collator.compareString(0, sA, sB);
  754.                 return comparison * modifier;
  755.             }
  756.             return compareStrings;
  757.  
  758.         default:
  759.             function compareOther(sortEntryA, sortEntryB) {
  760.                 return 0;
  761.             }
  762.             return compareOther;
  763.     }
  764. }
  765.  
  766. function compareNativeTime(a, b) {
  767.     return (a < b ? -1 :
  768.             a > b ?  1 : 0);
  769. }
  770.  
  771. function nativeTimeOrNow(calDateTime) {
  772.     // Treat null/0 as 'now' when sort started, so incomplete tasks stay current.
  773.     // Time is computed once per sort (just before sort) so sort is stable.
  774.     if (calDateTime == null) {
  775.         return unifinderTreeView.sortStartedTime;
  776.     }
  777.     var ns = calDateTime.nativeTime;
  778.     if (ns == -62168601600000000) { // ns value for (0000/00/00 00:00:00)
  779.         return unifinderTreeView.sortStartedTime;
  780.     }
  781.     return ns;
  782. }
  783.  
  784. function refreshEventTree() {
  785.     if (isUnifinderHidden()) {
  786.         // If the unifinder is hidden, don't refresh the events to reduce needed
  787.         // getItems calls.
  788.         return;
  789.     }
  790.     var savedThis = this;
  791.     var refreshListener = {
  792.         mEventArray: new Array(),
  793.  
  794.         onOperationComplete: function rET_onOperationComplete(aCalendar,
  795.                                                               aStatus,
  796.                                                               aOperationType,
  797.                                                               aId,
  798.                                                               aDateTime) {
  799.             var refreshTreeInternalFunc = function() {
  800.                 refreshEventTreeInternal(refreshListener.mEventArray);
  801.             };
  802.             setTimeout(refreshTreeInternalFunc, 0);
  803.         },
  804.  
  805.         onGetResult: function rET_onGetResult(aCalendar,
  806.                                               aStatus,
  807.                                               aItemType,
  808.                                               aDetail,
  809.                                               aCount,
  810.                                               aItems) {
  811.             for (var i = 0; i < aCount; i++) {
  812.                 refreshListener.mEventArray.push(aItems[i]);
  813.             }
  814.         }
  815.     };
  816.  
  817.     var Today = new Date();
  818.     // Do this to allow all day events to show up all day long.
  819.     var StartDate = new Date(Today.getFullYear(),
  820.                              Today.getMonth(),
  821.                              Today.getDate(),
  822.                              0, 0, 0);
  823.     var EndDate;
  824.  
  825.     var ccalendar = getCompositeCalendar();
  826.     var filter = 0;
  827.  
  828.     filter |= ccalendar.ITEM_FILTER_TYPE_EVENT;
  829.  
  830.     var filterMenulist = document.getElementById("event-filter-menulist");
  831.     // Not all xul might be there yet...
  832.     if (!filterMenulist) {
  833.         return;
  834.     }
  835.     switch (filterMenulist.selectedItem.value) {
  836.         case "all":
  837.             StartDate = null;
  838.             EndDate = null;
  839.             break;
  840.  
  841.         case "today":
  842.             EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24) - 1);
  843.             break;
  844.  
  845.         case "next7Days":
  846.             EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24 * 8));
  847.             break;
  848.  
  849.         case "next14Days":
  850.             EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24 * 15));
  851.             break;
  852.  
  853.         case "next31Days":
  854.             EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24 * 32));
  855.             break;
  856.  
  857.         case "thisCalendarMonth":
  858.             // midnight on first day of this month
  859.             var startOfMonth = new Date(Today.getFullYear(), Today.getMonth(), 1, 0, 0, 0);
  860.             // midnight on first day of next month
  861.             var startOfNextMonth = new Date(Today.getFullYear(), (Today.getMonth() + 1), 1, 0, 0, 0);
  862.             // 23:59:59 on last day of this month
  863.             EndDate = new Date(startOfNextMonth.getTime() - 1000);
  864.             StartDate = startOfMonth;
  865.             break;
  866.  
  867.         case "future":
  868.             EndDate = null;
  869.             break;
  870.  
  871.         case "current":
  872.             var SelectedDate = currentView().selectedDay.jsDate;
  873.             StartDate = new Date(SelectedDate.getFullYear(), SelectedDate.getMonth(), SelectedDate.getDate(), 0, 0, 0);
  874.             EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24) - 1000);
  875.             break;
  876.  
  877.         default:
  878.             dump("there's no case for " + filterMenulist.selectedItem.value + "\n");
  879.             EndDate = StartDate;
  880.             break;
  881.     }
  882.     gStartDate = StartDate ? jsDateToDateTime(StartDate).getInTimezone(calendarDefaultTimezone()) : null;
  883.     gEndDate = EndDate ? jsDateToDateTime(EndDate).getInTimezone(calendarDefaultTimezone()) : null;
  884.     if (StartDate && EndDate) {
  885.         filter |= ccalendar.ITEM_FILTER_CLASS_OCCURRENCES;
  886.     }
  887.  
  888.     ccalendar.getItems(filter, 0, gStartDate, gEndDate, refreshListener);
  889. }
  890.  
  891. function refreshEventTreeInternal(eventArray) {
  892.     var searchText = document.getElementById("unifinder-search-field").value;
  893.  
  894.     unifinderTreeView.setItems(eventArray.filter(isItemInFilter));
  895.  
  896.     // Select selected events in the tree. Not passing the argument gets the
  897.     // items from the view.
  898.     unifinderTreeView.setSelectedItems();
  899. }
  900.  
  901. function isItemInFilter(aItem) {
  902.     var searchText = document.getElementById("unifinder-search-field")
  903.                              .value.toLowerCase();
  904.  
  905.     if (!searchText.length || searchText.match(/^\s*$/)) {
  906.         return true;
  907.     }
  908.  
  909.     const fieldsToSearch = ["SUMMARY", "DESCRIPTION", "LOCATION", "CATEGORIES", "URL"];
  910.     if (!fixAlldayDates(aItem)) {
  911.         return false;
  912.     }
  913.  
  914.     for each (var field in fieldsToSearch) {
  915.         var val = aItem.getProperty(field);
  916.         if (val && val.toLowerCase().indexOf(searchText) != -1) {
  917.             return true;
  918.         }
  919.     }
  920.     return false;
  921. }
  922.  
  923. function focusSearch() {
  924.     document.getElementById("unifinder-search-field").focus();
  925. }
  926.  
  927. function toggleUnifinder() {
  928.     // Toggle the elements
  929.     goToggleToolbar('bottom-events-box', 'calendar_show_unifinder_command');
  930.     goToggleToolbar('calendar-view-splitter');
  931.  
  932.     unifinderTreeView.treeElement.view = unifinderTreeView;
  933.  
  934.     // When the unifinder is hidden, refreshEventTree is not called. Make sure
  935.     // the event tree is refreshed now.
  936.     if (!isUnifinderHidden() && gUnifinderNeedsRefresh) {
  937.         gUnifinderNeedsRefresh = false;
  938.         refreshEventTree();
  939.     }
  940.  
  941.     // Make sure the selection is correct
  942.     if (unifinderTreeView.doingSelection) {
  943.         unifinderTreeView.resetAllowSelection();
  944.     }
  945.     unifinderTreeView.setSelectedItems();
  946. }
  947.  
  948. window.addEventListener("load", prepareCalendarUnifinder, false);
  949. window.addEventListener("unload", finishCalendarUnifinder, false);
  950.